/**
 * 
 */
package problem4;

import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

//kümmert sich um die Beschaffung von Kunden, 
//die dann in einer WaitingQueue (Warteschlange) 
//eingereiht werden
public class Acquisition implements Runnable {

	private CopyOnWriteArrayList<Cashpoint> cashpoints;

	private boolean acquisition;

	private Cashpoint cashpoint;
	
	private Random random = new Random();

	public Acquisition() {

		// Kassen
		// Geschützte Arraylist für die Kassen
		cashpoints = new CopyOnWriteArrayList<Cashpoint>();

		// Sechs Kassen erstellen (zu Beginn alle geschlossen)
		for (int i = 0; i < 6; i++) {
			int j = i + 1;
			cashpoints.add(new Cashpoint("Kasse " + j, j, cashpoints));
			System.out.println("Erstelle " + cashpoints.get(i).getName());
		}

	}

	@Override
	public void run() {

		// Akquisition läuft
		acquisition = true;

		// Öffnen der ersten Kasse
		Thread t1 = new Thread(cashpoints.get(0));
		System.out.println("Wir öffnen " + cashpoints.get(0).getName());
		cashpoints.get(0).setOpen(true);
		t1.start();

		// Durchlaufen bis keine Akquise mehr stattfinden soll
		while (acquisition) {

			// Wartezeit zwischen 0 und 2 Sekunden
			try {
				Thread.sleep((int) (2000 * random.nextDouble()));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			// Neuen Kunden akquirieren
			Customer customer = new Customer("Kunde " + Math.random());

			int numberOfCustomers = Integer.MAX_VALUE;

			// Kritischer Bereich
			// Weiterer cashpoints-Lock in Cashpoint beim Entfernen von Kunden
			synchronized (cashpoints) {				

				// Aktive Kasse öffnen bzw. holen
				for (Cashpoint cash : cashpoints) {
					int size = cash.getCustomers().size();
					// Prüfen, ob neue Kasse aufgemacht werden muss
					if (size == 6) {
						// neue Kasse öffnen, falls 6 Kunden anstehen
						Cashpoint cashpointNew = openCashpoint(); // liefert null, wenn bereits alle geöffnet sind
						if (cashpointNew != null) {
							cashpoint = cashpointNew;
							numberOfCustomers = 0;
							break;
							// Wenn neue Kasse aufgemacht wurde, ist diese die kürzeste (mit 0 Kunden)
							// und die Schleife kann abgebrochen werden
						}
					}
					// Dann gucken, welches die offene Kasse mit den wenigsten Kunden ist
					if (cash.isOpen() && size < numberOfCustomers) {
						numberOfCustomers = size;
						cashpoint = cash;
					}
				}
				// Eigentlich müsste Sperrung der aktiven Kasse hier gestartet werden.
				// Geht nicht (mit unseren Kenntnissen) mit globaler sperre mit sync.
				// Laut Jens K. für die Abgabe in Ordnung und führt zu keinem Punktabzug.
			}

			// Kunde hinzufügen
			// Intrinsischer Lock: Kundenliste sperren
			// Weiterer customers-Lock in Cashpoint beim Abarbeiten der Kunden/Schließen der Kasse
			synchronized (cashpoint.getCustomers()) {				
				cashpoint.addCustomer(customer);
				int size = cashpoint.getCustomers().size();

				System.out.println(" Acquisition aquirierte " + customer.getName()
						+ " und reihte ihn bei " + cashpoint.getName() + " an Position " + size
						+ " ein.");

				numberOfCustomers++;

				// Auf Fehler, der auf "Synchronized-Problematik" (s.o.) beruht, hinweisen, sofern er auftritt
				if (size != numberOfCustomers) {
					System.out
							.println("Hier ist ein Fehler aufgrund der Problematik mit der Synchronized-Sperre aufgetreten."
									+ " Eigentlich hätte der Kunde an Position "
									+ numberOfCustomers + " eingereiht werden sollen.");
				}

			}

			// Abbruchbedingung für Akquise prüfen
			if (numberOfCustomers == 8) {
				acquisition = false; // Akquise soll beendet werden
				System.out.println("Das Geschäft schließt in Kürze!");
			}
		}

	}

	private Cashpoint openCashpoint() {
		for (Cashpoint cash : cashpoints) {
			// wenn es eine Kasse gibt, die noch nicht geöffnet ist
			if (!cash.isOpen()) {
				// Kasse öffnen
				cash.setOpen(true);
				System.out.println("Wir öffnen " + cash.getName());
				new Thread(cash).start();
				return cash; // neu eröffnete Kasse zurückgeben
			}
		}
		return null; // null zurückgeben, wenn bereits alle Kassen geöffnet sind
	}

}
